# This mixin captures the semantics of a monad. Including
# modules MUST implement mreturn and mbind.
module Monad
  def self.included(mod)
    mod.send :include, InstanceMethods
    mod.extend ClassMethods
  end  
  
  module InstanceMethods
    def mbind
      raise NotImplementedError, 'A concrete Monad must implement mbind'
    end
    
    def mfail(error)
      raise RuntimeError, error
    end
    
    def mseq
      mbind { |x| yield }
    end
    
    def mreturn(val)
      self.class.mreturn(val)
    end
  end
  
  module ClassMethods
    def mreturn(value)
      raise NotImplementedError, 'A concrete Monad must implement mreturn'
    end
  end
end

# Identity monad
class Id
  include Monad
  
  attr :value
  
  def initialize(value)
    @value = value
  end
  
  def self.mreturn(value)
    Id.new(value)
  end
  
  def mbind
    yield @value
  end
  
  def ==(other)
    case other
      when Id: @value == other.value
      else
        raise TypeError, "Cannot compare types Id and %s" % other.class.name
    end
  end
end

# Array monad
class Array
  include Monad
  
  def self.mreturn(value) [value] end
  def mbind(&f) map(&f).mjoin end
  def mjoin
    inject([]) { |joined, e| joined + e }
  end
end
